package com.cl.unipay4j.wx.pay;

import com.cl.unipay4j.core.*;
import com.cl.unipay4j.core.pay.IRefund;
import com.cl.unipay4j.core.pay.RefundModel;
import com.cl.unipay4j.core.pay.RefundQueryModel;
import com.cl.unipay4j.core.pay.RefundResponse;
import com.cl.unipay4j.core.util.MoneyUtil;
import com.cl.unipay4j.core.util.RequestUtil;
import com.cl.unipay4j.core.util.StrUtil;
import com.cl.unipay4j.wx.AbstractWxPay;
import com.cl.unipay4j.wx.RefundStateEnum;
import com.cl.unipay4j.wx.sdk.WXPayConstants;
import com.cl.unipay4j.wx.sdk.WXPayUtil;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * 微信支付退款
 * <p>退款</p>
 * 当交易发生之后一段时间内，由于买家或者卖家的原因需要退款时，卖家可以通过退款接口将支付款退还给买家，微信支付将在收到退款请求并且验证成功之后，按照退款规则将支付款按原路退到买家帐号上。
 * <p>
 * 注意：
 * <p>
 * 1、交易时间超过一年的订单无法提交退款
 * <p>
 * 2、微信支付退款支持单笔交易分多次退款，多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交，请不要更换退款单号，请使用原商户退款单号
 * <p>
 * 3、请求频率限制：150qps，即每秒钟正常的申请退款请求次数不超过150次错误或无效请求频率限制：6qps，即每秒钟异常或错误的退款申请请求不超过6次
 * <p>
 * 4、每个支付订单的部分退款次数不能超过50次
 * <p>
 * 5、如果同一个用户有多笔退款，建议分不同批次进行退款，避免并发退款导致退款失败
 *
 * @Author Small
 * @Date 2020/7/21 14:36
 * @Version 1.0
 **/
@Slf4j
public class WxPayRefund extends AbstractWxPay implements IRefund {

    public WxPayRefund() {
        super();
    }

    public WxPayRefund(IPayConfig payConfig) {
        super(payConfig);
    }

    /**
     * 退款请求
     *
     * @param model
     * @return
     */
    @Override
    public Result execRefund(RefundModel model) {
        log.info("WxRefund::=>退款请求到达,退款单号{}", model.getOutRefundNo());
        try {
            Map<String, String> r = initWxPay().refund(wrapRefundParam(model));
            String returnCode = r.get(WXPayConstants.RETURN_CODE);
            if (UnipayConst.SUCCESS.equals(returnCode)) {
                String resultCode = r.get(WXPayConstants.RESULT_CODE);
                if (UnipayConst.SUCCESS.equals(resultCode)) {
                    log.info("WxRefund::=>退款提交业务成功,退款单号{}", model.getOutRefundNo());
                    return Result.tradeSuccess(wrapperRefundResponse(r,true));
                } else {
                    String errCode = r.get(WXPayConstants.ERR_CODE);
                    String errCodeDes = r.get(WXPayConstants.ERR_CODE_DES);
                    log.info("WxRefund::=>退款提交业务失败,退款单号{}，错误代码{}，错误原因{}", model.getOutRefundNo(), errCode, errCodeDes);
                    return Result.returnFail(errCode, errCodeDes);
                }
            } else {
                String returnMsg = r.get(WXPayConstants.RETURN_MSG);
                log.info("WxRefund::=>退款失败,退款单号{}，错误代码{}，错误原因{}", model.getOutRefundNo(), returnCode, returnMsg);
                return Result.returnFail(returnCode, returnMsg);
            }
        } catch (Exception e) {
            log.warn("WxRefund::=>退款异常,退款单号{}", model.getOutRefundNo(), e);
        }
        return Result.exception();
    }



    /**
     * 退款查询
     * <p>
     * 仅支持根据退款单号查询单笔退款是否成功，不支持根据交易流水号查询批量退款结果
     *
     * @param model
     * @return
     */
    @Override
    public Result execQuery(RefundQueryModel model) {
        log.info("WxRefund::=>退款查询请求到达,退款单号{}", model.getOutRefundNo());
        try {
            Map<String, String> r = initWxPay().refundQuery(wrapQueryParam(model));
            String returnCode = r.get(WXPayConstants.RETURN_CODE);
            if (UnipayConst.SUCCESS.equals(returnCode)) {
                String resultCode = r.get(WXPayConstants.RESULT_CODE);
                if (UnipayConst.SUCCESS.equals(resultCode)) {
                    String refund_status_0 = r.get("refund_status_0");
                    if (RefundStateEnum.SUCCESS.getStatus().equals(refund_status_0)) {
                        return Result.tradeSuccess(wrapperRefundResponse(r,true));
                    } else {
                        return Result.tradeFail(refund_status_0, null);
                    }
                } else {
                    String errCode = r.get(WXPayConstants.ERR_CODE);
                    String errCodeDes = r.get(WXPayConstants.ERR_CODE_DES);
                    log.info("WxRefund::=>退款查询请求失败,退款单号{}，错误代码{}，错误原因{}", model.getOutRefundNo(), errCode, errCodeDes);
                    return Result.returnFail(errCode, errCodeDes);
                }
            } else {
                String returnMsg = r.get(WXPayConstants.RETURN_MSG);
                log.info("WxRefund::=>退款查询失败,退款单号{}，错误代码{}，错误原因{}", model.getOutRefundNo(), returnCode, returnMsg);
                return Result.returnFail(returnCode, returnMsg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Result.exception();
    }

    /**
     * 退款结果通知
     *
     * @param request
     * @return
     */
    @Override
    public Result<Object> execNotify(HttpServletRequest request) {
        try {
            String xmlStr = RequestUtil.getStrFromInputSteam(request.getInputStream());
            Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
            String returnCode = map.get(WXPayConstants.RETURN_CODE);
            if (UnipayConst.SUCCESS.equals(returnCode)) {
                // 加密字段
                String reqInfo = map.get("req_info");
                String reqInfoXml = WXPayUtil.decryptRefundReqInfo(reqInfo, wxPayConfig.getKey());
                Map<String, String> r = WXPayUtil.xmlToMap(reqInfoXml);
                String refundStatus = r.get("refund_status");
                if (UnipayConst.SUCCESS.equals(refundStatus)) {
                    return Result.tradeSuccess(r);
                } else {
                    return Result.tradeFail(refundStatus, null);
                }

            } else {
                // 通信失败
                return Result.returnFail(returnCode, map.get(WXPayConstants.RETURN_MSG));
            }
            // 解析微信回调参数
        } catch (Exception e) {
            log.warn("WxRefundNotify::=> 回调时发生异常", e);
        }
        return Result.exception();
    }

    /**
     * 回调成功，同步返回给支付平台的报文
     *
     * @return
     */
    @Override
    public String successMsg() {
        return "<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>";
    }

    /**
     * 回调失败，同步返回给支付平台的报文
     *
     * @return
     */
    @Override
    public String failMsg() {
        return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[ERROR]]></return_msg></xml>";
    }



    /**
     * 封装退款请求参数
     *
     * @param model
     * @return
     */
    private Map<String, String> wrapRefundParam(RefundModel model) {
        Map<String, String> param = new HashMap<>();
        if (StrUtil.isNotEmpty(model.getTradeNo())) {
            param.put("transaction_id", model.getTradeNo());
        }
        if (StrUtil.isNotEmpty(model.getOutTradeNo())) {
            param.put("out_trade_no", model.getOutTradeNo());
        }
        param.put("out_refund_no", model.getOutRefundNo());
        param.put("total_fee", String.valueOf(MoneyUtil.yuan2Cent(model.getOrderPrice())));
        param.put("refund_fee", String.valueOf(MoneyUtil.yuan2Cent(model.getRefundAmount())));
        param.put("refund_desc", model.getRefundReason());
        return param;
    }

    /**
     * 封装查询请求参数
     *
     * @param model
     * @return
     */
    private Map<String, String> wrapQueryParam(RefundQueryModel model) {
        Map<String, String> param = new HashMap<>();
        if (StrUtil.isNotEmpty(model.getOutRefundNo())) {
            param.put("out_refund_no", model.getOutRefundNo());
        }
        if (StrUtil.isNotEmpty(model.getRefundNo())) {
            param.put("refund_id", model.getRefundNo());
        }
        return param;
    }


    /**
     * 封装退款返回参数
     * @param r  微信返回报文
     * @param success 是否成功
     * @return
     */
    private RefundResponse wrapperRefundResponse(Map<String, String> r,boolean success){
        RefundResponse response = new RefundResponse();
        response.setPayChannel(PayChannel.WX);
        if (success){
            response.setOutRefundNo(r.get("out_refund_no"));
            response.setOutTradeNo(r.get("out_trade_no"));
            response.setRefundAmount(MoneyUtil.cent2Yuan(Integer.parseInt(r.get("refund_fee"))));
            response.setRefundNo(r.get("refund_id"));
            response.setTradeNo(r.get("transaction_id"));
        }else{

        }

        return response;
    }
}
